package rad.framework.seam;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;

import rad.framework.jpa.EntityPersistenceContextMap;
import rad.framework.lang.ReflectionUtil;
import rad.framework.security.jpa.PermissionsUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.Component;
import org.jboss.seam.Seam;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Expressions.ValueExpression;

/**
 * Metadata for populating a picker or combo or suggestion box.
 * 
 * @author boudyacho
 * 
 */
public class BaseReferenceQuery<COMP, TGT, SRC> {

	private static final Log logger = LogFactory.getLog(BaseReferenceQuery.class);
	
	public BaseReferenceQuery(Object component, String name) {
		this.name = name;
		this.setComponentName(component);
		this.setInstanceName(component);
	}
	
	public BaseReferenceQuery(Object component, String instanceName, String name) {
		this.name = name;
		this.instanceName = instanceName;
		this.setComponentName(component);
	}
	
	// --------------------------------------

	private String name;
	
	public String getName() {
		return name;
	}

	private String componentName;

	protected String getComponentName() {
		return componentName;
	}

	protected void setComponentName(Object component) {
		if (component != null) {
			this.componentName = Seam.getComponentName(component.getClass());
			// TODO store component class ???
		}
	}

	private String instanceName;
	
	protected String getInstanceName() {
		if (instanceName == null) {
			instanceName = getComponentName() + ".instance";
		}
		return instanceName;
	}

	protected void setInstanceName(Object component) {
		if (component != null) {
			instanceName = StringUtils.uncapitalize(ReflectionUtil.getParameterizedType(
					component.getClass(), 0).getSimpleName());
		}
	}

	//--------------------------------------
	
	public boolean isRendered() {
		return true;
	}
	
	// --------------------------------------

	protected String getEql() {
		StringBuilder builder = new StringBuilder();
		addSelect(builder);
		addFrom(builder);
		addJoin(builder);
		addWhere(builder);
		addOrderBy(builder);
		
		String eql = builder.toString();
		if (logger.isDebugEnabled()) {
			logger.debug(getName() + " Eql: " + eql);
		}
		return eql;
	}

	protected void addSelect(StringBuilder builder) {
		builder.append("select ");
		if (isDistinct()) {
			builder.append("distinct ");
		}
		builder.append(getAlias());
		if (isDistinct()) {
			builder.append(", " + getOrderBy());
		}
	}

	protected void addFrom(StringBuilder builder) {
		builder.append(" from ");
		builder.append(getFrom());
		builder.append(getFrom2());
	}

	protected void addJoin(StringBuilder builder) {
		builder.append(getJoin());
	}

	protected void addWhere(StringBuilder builder) {
		int count = 0;
		count = addPermissions(builder, count);
		count = addRestrictions(builder, count);
		count = addExtension(builder, count);
		count = addFilter(builder, count);
	}

	protected int addPermissions(StringBuilder builder, int count) {
		String permissions = PermissionsUtil.getFilter(getTargetEntityClass(), getAlias());
		if (!StringUtils.isEmpty(permissions)) {
			count = addConjunction(builder, count);
			builder.append(permissions);
		}
		return count;
	}

	protected int addRestrictions(StringBuilder builder, int count) {
		for (String restriction : restrictions) {
			count = addConjunction(builder, count);
			builder.append(restriction);
		}
		return count;
	}

	protected int addExtension(StringBuilder builder, int count) {
		return count;
	}
	
	protected int addFilter(StringBuilder builder, int count) {
		if (isFiltered()) {
			count = addConjunction(builder, count);
			builder.append(getFilterRestriction());
		}
		return count;
	}

	protected int addConjunction(StringBuilder builder, int count) {
		if (count++ > 0) {
			builder.append(" and ");
		} else {
			builder.append(" where ");
		}
		return count;
	}

	protected void addOrderBy(StringBuilder builder) {
		builder.append(" order by ");
		builder.append(getOrderBy());
	}

	// --------------------------------------

	protected boolean isDistinct() {
		return false;
	}

	protected String getAlias() {
		return "t1";
	}

	protected String getFrom() {
		return getTargetEntityName() + " " + getAlias();
	}

	protected String getFrom2() {
		return "";
	}

	protected String getJoin() {
		return "";
	}

	protected String getOrderBy() {
		return "t1.name";
	}
	
	// --------------------------------------

	protected Class getSourceEntityClass() {
		// TODO calc or change code gen
		// if getInstance() not null getInstance().getClass()
		return null;
	}

	public String getSourceEntityName() {
		return getSourceEntityClass().getSimpleName();
	}

	protected Class getTargetEntityClass() {
		// TODO calc or change code gen
		return getEntityClass();
	}

	public String getTargetEntityName() {
		return getTargetEntityClass().getSimpleName();
	}

	/** @deprecated*/
	protected Class getEntityClass() {
		return null;
	}

	/** @deprecated*/
	public String getEntityName() {
		return getEntityClass().getSimpleName();
	}

	// --------------------------------------
	
	protected String getItemValue() {
		return "#{t1.name}";
	}
	
	protected String getItemLabel() {
		return "#{t1.name}";
	}

	// --------------------------------------
	
	private List<String> restrictions = new ArrayList<String>();

	protected void _addRestriction(String restriction) {
		// t1.parent = #{myEntity}
		this.restrictions.add(restriction);
	}

	// --------------------------------------

	public boolean isFiltered() {
		return false;
	}

	protected String getFilterParamName() {
		int i = getFilterRestriction().indexOf(":");
		String name = getFilterRestriction().substring(i+1);
		i = name.indexOf(" ");
		if (i >= 0) {
			name = name.substring(0, i);
		}
		return name;
	}
	
	protected String getFilterRestriction() {
		return "lower(t1.name) like :filter";
	}

	public String getWildcardFilter(String filter) {
		// TODO replace * with % ?
		if (filter == null || filter.length() == 0) {
			return "%";
		} else {
			return filter.trim().toLowerCase() + '%';
		}
	}

	// --------------------------------------

	protected String getPersistenceContextName() {
		// TODO store
		return EntityPersistenceContextMap.instance()
				.getPersistenceContextNameFor(getTargetEntityClass());
	}

	public EntityManager getEntityManager() {
		// TODO store
		return (EntityManager) Component.getInstance(getPersistenceContextName());
	}
	
	protected void flush() {
		getEntityManager().flush();
	}

	// --------------------------------------

	protected COMP getComponent() {
		return (COMP) Component.getInstance(getComponentName());
	}
	
	protected SRC getInstance() {
		if (instanceExp == null) {
			instanceExp = getExpression("#{" + getInstanceName() + "}");
		}
		return (SRC)instanceExp.getValue();
	}

	private ValueExpression instanceExp;

	// --------------------------------------
	
	protected ValueExpression getExpression(String exp) {
		return Expressions.instance().createValueExpression(exp);
	}
	
	protected List<SelectItem> sort(List<SelectItem> list) {
		// TODO generate override if already sorted
		Collections.sort(list, new Comparator<SelectItem>() {
			public int compare(SelectItem lh, SelectItem rh) {
				return lh.getLabel().compareTo(rh.getLabel());
			}
		});
		return list;
	}
}
